// SignalClosure.cs - signal marshaling class
//
// Authors: Mike Kestner <mkestner@novell.com>
//
// Copyright (c) 2008 Novell, Inc.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 2 of the Lesser GNU General 
// Public License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.


namespace GLib {

	using System;
	using System.Collections;
	using System.Runtime.InteropServices;

	internal class ClosureInvokedArgs : EventArgs {

		EventArgs args;
		GLib.Object obj;

		public ClosureInvokedArgs(GLib.Object obj, EventArgs args) {
			this.obj = obj;
			this.args = args;
		}

		public EventArgs Args {
			get {
				return args;
			}
		}

		public GLib.Object Target {
			get {
				return obj;
			}
		}
	}

	struct GClosure {
		public long fields;
		public IntPtr marshaler;
		public IntPtr data;
		public IntPtr notifiers;
	}

	internal delegate void ClosureInvokedHandler(object o, ClosureInvokedArgs args);

	internal class SignalClosure : IDisposable {

		IntPtr handle;
		IntPtr raw_closure;
		string name;
		uint id = UInt32.MaxValue;
		System.Type args_type;
		Delegate custom_marshaler;
		GCHandle gch;

		static Hashtable closures = new Hashtable();

		public SignalClosure(IntPtr obj, string signal_name, System.Type args_type) {
			raw_closure = g_closure_new_simple(Marshal.SizeOf(typeof(GClosure)), IntPtr.Zero);
			g_closure_set_marshal(raw_closure, Marshaler);
			g_closure_add_finalize_notifier(raw_closure, IntPtr.Zero, Notify);
			closures[raw_closure] = this;
			handle = obj;
			name = signal_name;
			this.args_type = args_type;
		}

		public SignalClosure(IntPtr obj, string signal_name, Delegate custom_marshaler, Signal signal) {
			gch = GCHandle.Alloc(signal);
			raw_closure = g_cclosure_new(custom_marshaler, (IntPtr)gch, Notify);
			closures[raw_closure] = this;
			handle = obj;
			name = signal_name;
			this.custom_marshaler = custom_marshaler;
		}

		public event EventHandler Disposed;
		public event ClosureInvokedHandler Invoked;

		public void Connect(bool is_after) {
			IntPtr native_name = GLib.Marshaller.StringToPtrGStrdup(name);
			id = g_signal_connect_closure(handle, native_name, raw_closure, is_after);
			GLib.Marshaller.Free(native_name);
		}

		public void Disconnect() {
			if (id != UInt32.MaxValue && g_signal_handler_is_connected(handle, id))
				g_signal_handler_disconnect(handle, id);
		}

		public void Dispose() {
			Disconnect();
			closures.Remove(raw_closure);
			if (custom_marshaler != null)
				gch.Free();
			custom_marshaler = null;
			if (Disposed != null)
				Disposed(this, EventArgs.Empty);
			GC.SuppressFinalize(this);
		}

		public void Invoke(ClosureInvokedArgs args) {
			if (Invoked == null)
				return;
			Invoked(this, args);
		}

		static ClosureMarshal marshaler;
		static ClosureMarshal Marshaler {
			get {
				if (marshaler == null)
					marshaler = new ClosureMarshal(MarshalCallback);
				return marshaler;
			}
		}

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		delegate void ClosureMarshal(IntPtr closure, IntPtr return_val, uint n_param_vals, IntPtr param_values, IntPtr invocation_hint, IntPtr marshal_data);

		static void MarshalCallback(IntPtr raw_closure, IntPtr return_val, uint n_param_vals, IntPtr param_values, IntPtr invocation_hint, IntPtr marshal_data) {
			string message = String.Empty;

			try {
				SignalClosure closure = closures[raw_closure] as SignalClosure;
				message = "Marshaling " + closure.name + " signal";
				Value objval = (Value)Marshal.PtrToStructure(param_values, typeof(Value));
				GLib.Object __obj = objval.Val as GLib.Object;
				if (__obj == null)
					return;

				if (closure.args_type == typeof(EventArgs)) {
					closure.Invoke(new ClosureInvokedArgs(__obj, EventArgs.Empty));
					return;
				}

				SignalArgs args = Activator.CreateInstance(closure.args_type, new object[0]) as SignalArgs;
				args.Args = new object[n_param_vals - 1];
				GLib.Value[] vals = new GLib.Value[n_param_vals - 1];
				for (int i = 1; i < n_param_vals; i++) {
					IntPtr ptr = new IntPtr(param_values.ToInt64() + i * Marshal.SizeOf(typeof(Value)));
					vals[i - 1] = (Value)Marshal.PtrToStructure(ptr, typeof(Value));
					args.Args[i - 1] = vals[i - 1].Val;
				}
				ClosureInvokedArgs ci_args = new ClosureInvokedArgs(__obj, args);
				closure.Invoke(ci_args);
				for (int i = 1; i < n_param_vals; i++) {
					vals[i - 1].Update(args.Args[i - 1]);
					IntPtr ptr = new IntPtr(param_values.ToInt64() + i * Marshal.SizeOf(typeof(Value)));
					Marshal.StructureToPtr(vals[i - 1], ptr, false);
				}
				if (return_val == IntPtr.Zero || args.RetVal == null)
					return;

				Value ret = (Value)Marshal.PtrToStructure(return_val, typeof(Value));
				ret.Val = args.RetVal;
				Marshal.StructureToPtr(ret, return_val, false);
			} catch (Exception e) {
				Console.WriteLine(message);
				ExceptionManager.RaiseUnhandledException(e, false);
			}
		}

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		delegate void ClosureNotify(IntPtr data, IntPtr closure);

		static void NotifyCallback(IntPtr data, IntPtr raw_closure) {
			SignalClosure closure = closures[raw_closure] as SignalClosure;
			if (closure != null)
				closure.Dispose();
		}

		static ClosureNotify notify_handler;
		static ClosureNotify Notify {
			get {
				if (notify_handler == null)
					notify_handler = new ClosureNotify(NotifyCallback);
				return notify_handler;
			}
		}

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_cclosure_new(Delegate cb, IntPtr user_data, ClosureNotify notify);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern IntPtr g_closure_new_simple(int closure_size, IntPtr dummy);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_closure_set_marshal(IntPtr closure, ClosureMarshal marshaler);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_closure_add_finalize_notifier(IntPtr closure, IntPtr dummy, ClosureNotify notify);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern uint g_signal_connect_closure(IntPtr obj, IntPtr name, IntPtr closure, bool is_after);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern void g_signal_handler_disconnect(IntPtr instance, uint handler);

		[DllImport(Global.GObjectNativeDll, CallingConvention = CallingConvention.Cdecl)]
		static extern bool g_signal_handler_is_connected(IntPtr instance, uint handler);
	}
}